package org.jadelyre.lyre;

import org.jadelyre.core.*;
import org.jadelyre.core.codec.Codec;
import org.jadelyre.core.codec.sun.SunCodec;
import org.jadelyre.core.exception.JadelyreException;
import org.jadelyre.core.exception.LyreException;
import org.jadelyre.core.worker.WorkerFactory;
import org.jadelyre.utils.Assert;
import org.jadelyre.utils.StrUtils;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class LyreClient extends AbstractJadeLyre {

    private final ClientConfig clientConfig;
    private AsynchronousSocketChannel asynchronousSocketChannel;
    private CountDownLatch countDownLatch = new CountDownLatch(1);

    public LyreClient(ClientConfig clientConfig, Handler handler) throws JadelyreException {
        this(clientConfig,handler,new SunCodec());
    }
    public LyreClient(ClientConfig clientConfig, Handler handler, Codec codec) throws JadelyreException {
        super(handler, codec);
        Assert.notNull(clientConfig,"ClientConfig is required,must not be null");
        Assert.notNull(handler,"Handler is required,must not be null");
        this.clientConfig = clientConfig;
    }

    public Channel connect() {
        try {
            asynchronousSocketChannel = AsynchronousSocketChannel.open(asynchronousChannelGroup);
            asynchronousSocketChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);
            asynchronousSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
            asynchronousSocketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
            if (clientConfig.getBindPort() != null) {
                InetSocketAddress inetSocketAddress = StrUtils.isBlank(clientConfig.getBindHost()) ? new InetSocketAddress(clientConfig.getBindPort())
                        : new InetSocketAddress(clientConfig.getBindHost(), clientConfig.getBindPort());
                asynchronousSocketChannel.bind(inetSocketAddress);
            }
        } catch (IOException e) {
            throw new LyreException(e);
        }
        NodeAddress nodeAddress = new NodeAddress(clientConfig.getServerHost(), clientConfig.getServerPort());
        LyreChannel lyreChannel = new LyreChannel(this, asynchronousSocketChannel, nodeAddress);
        asynchronousSocketChannel.connect(new InetSocketAddress(clientConfig.getServerHost(), clientConfig.getServerPort()),
                lyreChannel, lyreChannel.getConnectionCompletionHandler());
        boolean await;
        try {
            await = countDownLatch.await(clientConfig.getConnectTimeout(), TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new LyreException(e);
        }
        if (!await) {
            throw new LyreException("无法连接远程节点，请确认连接配置正确！");
        }
        addShutdownHook();
//        if (clientConfig.getReconnectConfig().isEnable()){
//            Runnable runnable = clientConfig.getReconnectConfig().reconnectTask(lyreChannel);
//            getWorkerFactory().getScheduleWorker().schedule(runnable,5,TimeUnit.SECONDS);
//        }
        return lyreChannel;
    }

    public void reconnect(LyreChannel lyreChannel) {
        asynchronousSocketChannel.connect(new InetSocketAddress(clientConfig.getServerHost(), clientConfig.getServerPort()),
                lyreChannel, lyreChannel.getConnectionCompletionHandler());
    }

    @Override
    public WorkerFactory initWorker() {
        return new LyreWorkerFactory();
    }

    @Override
    protected AsynchronousChannelGroup createAsynchronousChannelGroup() throws LyreException {
        try {
            return AsynchronousChannelGroup
                    .withThreadPool(ThreadPool.createThreadPoolExecutor(ThreadPool.ThreadPoolEnum.LYRE_SYSTEM_CONNECT));
        } catch (IOException e) {
            throw new LyreException(e);
        }
    }

    @Override
    protected void stop() throws LyreException {
        started = false;
        try {
            asynchronousSocketChannel.close();
        } catch (IOException e) {
            throw new LyreException(e);
        }
        asynchronousChannelGroup.shutdown();
//        workerFactory.shutdown();
    }

    public CountDownLatch getCountDownLatch() {
        return countDownLatch;
    }
}
